package com.xiaoyang.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaoyang.common.core.constants.CacheConstants;
import com.xiaoyang.common.core.constants.Constants;
import com.xiaoyang.common.core.constants.HttpConstants;
import com.xiaoyang.common.core.domain.LoginUser;
import com.xiaoyang.common.core.domain.R;
import com.xiaoyang.common.core.domain.vo.LoginUserVO;
import com.xiaoyang.common.core.enums.ResultCode;
import com.xiaoyang.common.core.enums.UserIdentity;
import com.xiaoyang.common.core.enums.UserStatus;
import com.xiaoyang.common.core.utils.ThreadLocalUtil;
import com.xiaoyang.common.message.sender.SMSSender;
import com.xiaoyang.common.message.service.AliSmsService;
import com.xiaoyang.common.redis.service.RedisService;
import com.xiaoyang.common.security.exception.ServiceException;
import com.xiaoyang.common.security.service.TokenService;
import com.xiaoyang.friend.domain.user.User;
import com.xiaoyang.friend.domain.user.dto.UserDTO;
import com.xiaoyang.friend.domain.user.dto.UserUpdateDTO;
import com.xiaoyang.friend.domain.user.vo.UserVO;
import com.xiaoyang.friend.manager.UserCacheManager;
import com.xiaoyang.friend.mapper.user.UserMapper;
import com.xiaoyang.friend.service.user.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Autowired
    private SMSSender smsSender;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Value("${sms.templateId}")
    private String templateId;

    @Value("${sms.is-send:false}")
    private Boolean isSend;

    /**
     * 手机验证码过期时间
     */
    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;

    /**
     * 手机验证码发送限制次数
     */
    @Value("${sms.send-limit:3}")
    private Integer sendLimit;

    /**
     * 根据手机号 发送验证码
     * @param userDTO 手机号
     */
    @Override
    public boolean sendCode(UserDTO userDTO) throws Exception {
        log.info("发送手机验证码：{}", userDTO.getPhone());

        // 校验手机号是否合法
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }

        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        // 计算过期时间
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expire != null &&( phoneCodeExpiration * 60 - expire) < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }

        // 限制验证码发送次数
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        Long sendTime = redisService.getCacheObject(codeTimeKey, Long.class);
        if (sendTime != null && sendTime >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }

        // 生成6位随机验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;

        // 存储redis
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);

        // 发送验证码 -- 阿里云
//        boolean sendMobileCode = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
//        if (!sendMobileCode) {
//            throw new ServiceException(ResultCode.FAILED_SEND_CODE);
//        }

        if (isSend) {
            // 发送短信
            String sendMobileCode = smsSender.sendSMS(templateId, code, userDTO.getPhone());
            if (!sendMobileCode.equals("{\"code\": 200, \"msg\": \"请求成功\"}")) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }

        // 存储发送次数
        redisService.increment(codeTimeKey);

        if (sendTime == null) { // 当天首次发送验证码
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }

        return true;
    }

    /**
     * 手机验证码登录
     * @param phone
     * @param code
     */
    @Override
    public String codeLogin(String phone, String code) {
        log.info("手机验证码登录：{}", phone);

        // 判断验证码是否正确
        checkCode(phone, code);

        // 查询新老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if (user == null) {
            // 注册新用户
            user = new User();
            user.setNickName("OJ-" + phone); // 设置默认用户名
            user.setHeadImage("defaultavatar.jpg"); // 设置默认头像
            user.setPhone(phone);
            user.setStatus(UserStatus.Block.getValue());
            userMapper.insert(user); // 新增用户
        }

        // 登录成功--生成token
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName(), user.getHeadImage());
    }

    /**
     * 退出登录
     * @param token
     */
    @Override
    public boolean logout(String token) {
        log.info("退出登录：{}", token);
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.DeleteLoginUser(token, secret);
    }

    /**
     * 获取当前登录用户信息--获取用户头像、昵称
     * @param token
     * @return
     */
    @Override
    public R<LoginUserVO> info(String token, String secret) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        log.info("获取当前登录用户的头像和昵称：{}", userId);
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return R.fail();
        }

        // 返回给前端
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return R.ok(loginUserVO);
    }

    /**
     * 获取当前用户信息
     * @return
     */
    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);

        log.info("获取{}用户信息", userId);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    /**
     * 修改用户信息
     * @param userUpdateDTO
     * @return
     */
    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        log.info("修改{}信息", userId);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    /**
     * 修改用户头像
     * @param headImage
     * @return
     */
    @Override
    public int updateHeadImage(String headImage) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        log.info("修改用户头像：{}", headImage);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(headImage);
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    /**
     * 校验验证码是否正确
     * @param phone
     * @param code
     */
    private void checkCode(String phone, String code) {
        // 获取手机验证码的key
        String phoneCodeKey = getPhoneCodeKey(phone);
        // 获取redis中存储的验证码
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        // 验证码不存在或者已经是失效
        if (cacheCode == null) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        // 输入的验证码和redis中存储的验证码不匹配
        if (!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        // 验证码匹配，登录成功
        redisService.deleteObject(phoneCodeKey); // 删除redis中存储的验证码
    }

    /**
     * 手机验证码的key
     * @param phone
     * @return
     */
    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    /**
     * 手机验证码过期时间的key
     * @param phone
     * @return
     */
    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }

    /**
     * 校验手机号是否合法
     * @param phone 手机号
     * @return
     */
    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }
}
